﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using GBricks.Collections;

namespace GBricks.Tests.Collections
{
  [TestClass]
  public class ComparersTests
  {
    [TestMethod]
    public void ComparersExtensions() {
      Assert.IsTrue(Comparer<int>.Default.Equal(1, 1));
      Assert.IsFalse(Comparer<int>.Default.Equal(1, 0));

      Assert.IsFalse(Comparer<int>.Default.NotEqual(1, 1));
      Assert.IsTrue(Comparer<int>.Default.NotEqual(1, 0));

      Assert.IsTrue(Comparer<int>.Default.LessThan(0, 1));
      Assert.IsFalse(Comparer<int>.Default.LessThan(1, 0));
      Assert.IsFalse(Comparer<int>.Default.LessThan(1, 1));

      Assert.IsTrue(Comparer<int>.Default.LessThanOrEqual(0, 1));
      Assert.IsFalse(Comparer<int>.Default.LessThanOrEqual(1, 0));
      Assert.IsTrue(Comparer<int>.Default.LessThanOrEqual(1, 1));

      Assert.IsFalse(Comparer<int>.Default.GreaterThan(0, 1));
      Assert.IsTrue(Comparer<int>.Default.GreaterThan(1, 0));
      Assert.IsFalse(Comparer<int>.Default.GreaterThan(1, 1));

      Assert.IsFalse(Comparer<int>.Default.GreaterThanOrEqual(0, 1));
      Assert.IsTrue(Comparer<int>.Default.GreaterThanOrEqual(1, 0));
      Assert.IsTrue(Comparer<int>.Default.GreaterThanOrEqual(1, 1));
    }

    [TestMethod]
    public void ComparersDynamic() {
      var cases = new Dictionary<Type, IEqualityComparer> {
        { typeof(int), EqualityComparer<int>.Default },
        { typeof(string), StringComparer.OrdinalIgnoreCase },
      };

      var comparer = Comparers.Dynamic<object>(cases);
      Assert.IsTrue(comparer.Equals(1, 1));
      Assert.IsTrue(comparer.Equals("1", "1"));
      Assert.IsTrue(comparer.Equals("a", "A"));
      Assert.IsTrue(comparer.Equals(null, null));

      Assert.IsFalse(comparer.Equals(1, 2));
      Assert.IsFalse(comparer.Equals(1, "1"));
      Assert.IsFalse(comparer.Equals("1", 1));
      Assert.IsFalse(comparer.Equals("1", "2"));

      Assert.IsFalse(comparer.Equals(1, null));
      Assert.IsFalse(comparer.Equals(null, 1));
      Assert.IsFalse(comparer.Equals("1", null));
      Assert.IsFalse(comparer.Equals(null, "1"));

      var hash1 = comparer.GetHashCode(null);
      var hash2 = comparer.GetHashCode(1);
      var hash3 = comparer.GetHashCode("1");

      CheckComparerEquality(comparer);
    }

    [TestMethod, ExpectedExceptionAttribute(typeof(InvalidOperationException))]
    public void ComparersDynamicInvalidTypeException() {
      var cases = new Dictionary<Type, IEqualityComparer> { { typeof(int), EqualityComparer<int>.Default }, };
      var comparer = Comparers.Dynamic<object>(cases);
      Assert.IsTrue(comparer.Equals(1, 1));
      var hash4 = comparer.GetHashCode(new object());
    }

    [TestMethod]
    public void ComparersConst() {
      ComparersConst(-1, 0, 1);
      ComparersConst(-1, 2, 3);
      ComparersConst(0, 1, 2);
      ComparersConst(1, 2, 3);
      ComparersConst(0, Int32.MinValue, Int32.MaxValue);
      ComparersConst(0UL, UInt64.MinValue, UInt64.MaxValue);
      ComparersConst(null, new object(), new object());
      ComparersConst(null, String.Empty, typeof(Comparers).Name);
    }

    private void ComparersConst<T>(T value1, T value2, T value3) {
      ComparersConst(Comparers.True<T>(), true, null, value1, value2, value3);
      ComparersConst(Comparers.False<T>(), false, null, value1, value2, value3);
      ComparersConst(Comparers.Const<T>(true, 5), true, 5, value1, value2, value3);
      ComparersConst(Comparers.Const<T>(false, 7), false, 7, value1, value2, value3);

      CheckComparerEquality(Comparers.True<T>(), Comparers.False<T>());

      ComparersConst(Comparers.Zero<T>(), 0, value1, value2, value3);
      ComparersConst(Comparers.Const<T>(3), 3, value1, value2, value3);

      CheckComparerEquality(Comparers.Zero<T>(), Comparers.Const<T>(1));
    }

    private void ComparersConst<T>(IEqualityComparer<T> comparer, bool equals, int? hashCode, T value1, T value2, T value3) {
      Argument.NotNull(comparer, "comparer");

      Assert.AreEqual(comparer.Equals(value1, value2), equals);
      Assert.AreEqual(comparer.Equals(value1, value3), equals);
      Assert.AreEqual(comparer.Equals(value2, value3), equals);
      Assert.AreEqual(comparer.Equals(value2, value1), equals);
      Assert.AreEqual(comparer.Equals(value3, value1), equals);
      Assert.AreEqual(comparer.Equals(value3, value2), equals);

      if(hashCode != null) {
        Assert.AreEqual(comparer.GetHashCode(value1), hashCode.Value);
        Assert.AreEqual(comparer.GetHashCode(value2), hashCode.Value);
        Assert.AreEqual(comparer.GetHashCode(value3), hashCode.Value);
      }//if
    }

    private void ComparersConst<T>(IComparer<T> comparer, int compare, T value1, T value2, T value3) {
      Argument.NotNull(comparer, "comparer");

      Assert.AreEqual(comparer.Compare(value1, value2), compare);
      Assert.AreEqual(comparer.Compare(value1, value3), compare);
      Assert.AreEqual(comparer.Compare(value2, value3), compare);
      Assert.AreEqual(comparer.Compare(value2, value1), compare);
      Assert.AreEqual(comparer.Compare(value3, value1), compare);
      Assert.AreEqual(comparer.Compare(value3, value2), compare);
    }

    private class A { }
    private class B : A { }
    private class C : A { }
    private class D : B { }

    [TestMethod]
    public void ComparersByType() {
      Assert.AreEqual(Comparers.ByType.Compare(null, null), 0);
      Assert.IsTrue(Comparers.ByType.Compare(typeof(A), null) > 0);
      Assert.IsTrue(Comparers.ByType.Compare(null, typeof(A)) < 0);

      Assert.AreEqual(Comparers.ByType.Compare(typeof(A), typeof(A)), 0);
      Assert.AreEqual(Comparers.ByType.Compare(typeof(B), typeof(B)), 0);
      Assert.AreEqual(Comparers.ByType.Compare(typeof(C), typeof(C)), 0);
      Assert.AreEqual(Comparers.ByType.Compare(typeof(D), typeof(D)), 0);

      Assert.IsTrue(Comparers.ByType.Compare(typeof(A), typeof(B)) > 0);
      Assert.IsTrue(Comparers.ByType.Compare(typeof(B), typeof(A)) < 0);

      Assert.IsTrue(Comparers.ByType.Compare(typeof(A), typeof(D)) > 0);
      Assert.IsTrue(Comparers.ByType.Compare(typeof(D), typeof(A)) < 0);

      Assert.IsTrue(Comparers.ByType.Compare(typeof(B), typeof(D)) > 0);
      Assert.IsTrue(Comparers.ByType.Compare(typeof(D), typeof(B)) < 0);

      Assert.IsTrue(Comparers.ByType.Compare(typeof(B), typeof(C)) < 0);
      Assert.IsTrue(Comparers.ByType.Compare(typeof(C), typeof(B)) < 0);

      CheckComparerEquality(Comparers.ByType);
    }

    private void CheckEqualityComparerForNulls<T>(IEqualityComparer<T> comparer, T value) where T : class {
      Argument.NotNull(comparer, "comparer");
      Argument.NotNull(value, "value");

      Assert.IsTrue(comparer.Equals(null, null));
      Assert.IsFalse(comparer.Equals(value, null));
      Assert.IsFalse(comparer.Equals(null, value));
    }

    private void CheckComparerEquality<T>(T value, T other = null) where T : class {
      Argument.NotNull(value, "value");

      Assert.IsFalse(value.Equals(default(object)));
      Assert.IsTrue(value.Equals(value));

      if(other != null) {
        Assert.IsFalse(value.Equals(other));
      }//if

      var hash = value.GetHashCode();
    }

    [TestMethod]
    public void ComparersDictionary() {
      var d1 = new Dictionary<string, string>
      {
        { "One", "a" },
        { "Two", "b" },
        { "Three", "c" },
      };

      var d2 = new Dictionary<string, string>
      {
        { "One", "a" },
        { "Two", "B" },
        { "Three", "C" },
      };

      var d3 = new Dictionary<string, string>
      {
        { "1", "a" },
        { "2", "B" },
        { "3", "C" },
      };

      var d4 = new Dictionary<string, string>
      {
        { "One", "A" },
        { "Two", "B" },
      };

      var comparer1 = Comparers.Dictionary(EqualityComparer<string>.Default, StringComparer.OrdinalIgnoreCase);
      Assert.IsTrue(comparer1.Equals(d1, d2));
      Assert.AreEqual(comparer1.GetHashCode(d1), comparer1.GetHashCode(d2));
      Assert.IsFalse(comparer1.Equals(d1, d3));
      Assert.IsFalse(comparer1.Equals(d1, d4));

      var comparer2 = Comparers.Dictionary<string, string>();
      Assert.IsFalse(comparer2.Equals(d1, d2));

      CheckEqualityComparerForNulls(comparer1, d1);
      CheckComparerEquality(comparer1, comparer2);
    }
  }
}
